# Async Generator

ES2017 의 비동기함수가 도입되기 전, generator 가 비동기 프로그래밍을 위해 널리 사용되었다고한다.

일반함수

  • (0, 1)개의 값만 반환

# 일반 Generator

  • await 사용 불가

  • redux-saga 에서도 Generator 를 사용하고 있다.

  • 동기적 문법

  • 여러 개의 값을 필요에 따라 하나씩 yield 반환 한다.

  • 함수를 잠수 멈춰둘 수 있는 generator 의 특징을 이용해 비동기 프로그래밍을 위해 사용되기도 한다.

    const infinity = (function*() {
      let i = 0;
      while(true) yield i++;
    })();
    console.log(infinity.next());
    
    1
    2
    3
    4
    5
    • generator 가 아니며 while 이 무한으로 반복되고 있을 때
      • SyncFlow 에서는 CPU 의 blocking 이 계속 일어남
    • generator 인 경우 while 문을 yield 를 통해서 탈출 가능
  • 함수의 재개를 프로그래머가 직접 제어할 수 있다.

  • Suspend: Sync Flow 를 중단할 수 있다.

# nbFor 함수(일반함수) 를 generator 함수로 만들기

nbFor: load 값 조절을 통한 nonBlocking 함수.

const nbFor = (max, load, block) => {
  let i = 0;
  const f = time => {
    let curr = load;
    while(curr-- && i < max) {
      block();
      i++;
    }
    if (i < max - 1) requestAnimationFrame(f);
  };
  requestAnimationFrame(f);
};
1
2
3
4
5
6
7
8
9
10
11
12

generator 를 통해 requestAnimationFrame 와 같은 async 함수를 없애보자

  • 재귀호출 위치를 추적하지 않아도 된다.
  • syncFlow 로직이기 때문에 더 이해하기 쉽다.
  • async 함수 단점이 없어진다.
    • 어휘공간을 유지할 수 있어 복잡한 스코프를 고려하지 않아도 된다.
const gene = function*(max, load, block) {
    let i = 0, curr = load;
    while(i < max) {
        if(curr--) {
            block();
            i++;
        } else {
            curr = load;
            yield;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
  • 외부에서 제어할 수 있는 권한이 생겼다.
const nbFor = (max, load, block) => {
    const iterator = gene(max, load, block);
    const f = _ => iterator.next().done || requestAnimationFrame(f);
    requestAnimationFrame(f);
}
1
2
3
4
5

정리해 보자면

  1. 비동기 스타일의 콜백 loop 를 쓰는 것보다 동기적인 코드로 작성하기 편하다
  2. 실행부분과 Loop 의 관심사 분리

# Promise yield

Promise 자체는 즉시 yield 된다.

const gene2 = function*(max, load, block) {
    let i = 0;
    while (i < max) {
        yield new Promise(res => {
            let curr = load;
            while (curr-- && i < max) {
                block();
                i++;
            }
            // frame 단위로 resolve 실행
            requestAnimationFrame(res);
        })
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Promise 가 resolve 되는 순간에 iterator.next() 가 된다.

const nbFor = (max, load, block) => {
    const iterator = gene2(max, load, block);
    const next = ({ value, done }) => done || value.then(v => next(iterator.next()));
    next(iterator.next());
}
1
2
3
4
5

load 를 없애 버린 다음 코드는 block 일까 non block 일까?

const gene2 = function*(max, block) {
    let i = 0;
    while (i++ < max) {
        yield new Promise(res => { block(); res(); })
    }
}
1
2
3
4
5
6
  • Promise Jobs 때문에 cpu 가 멈추지는 않는다.
  • 마이크로 태스크 micro task
    • 하나의 프레임(애니매이션) 안에서 다시 비동기 타이밍을 나누어주는 역할
    • 한 프레임을 잡아두고 마이크로 태스크(promise job)를 실행하는데 해소될 때까지 프레임이 넘어가지 않는 현상이 발생한다.
    • script timeout 이 걸리지 않기 때문에 브라우저가 죽지 않는다.

nb 가 10000번이 찍혀야 f 가 찍힌다.

nbFor(10000, _ => console.log("nb"));
const f = _ => {
    console.log('f');
    requestAnimationFrame(f);
};
requestAnimationFrame(f);
1
2
3
4
5
6

다음의 경우 nb 가 어느정도 찍히다가 프레임이 넘어간다.

const f = _ => {
    console.log('f');
    requestAnimationFrame(f);
};
requestAnimationFrame(f);
nbFor(10000, _ => console.log("nb"));
1
2
3
4
5
6

requestAnimationFrame 와 같이 시간에 관련된 비동기 함수를 time 함수라고 부르겠다.

promise 안의 time 함수

const gene2 = function* (max, block) {
    let i = 0;
    while (i++ <max) yield new Promise(res => {
        block();
        time(res);
    })
}
1
2
3
4
5
6
7

time 함수의 분리

const gene2 = function* (max, block) {
    let i = 0;
    while (i++ <max) yield new Promise(res => {
        block();
        res();
    })
};

const nbFor = (max, block) => {
    const iterator = gene2(max, block);
    const next = ({ value, done }) => done || value.then(v => time(_ => next(iterator.next())));
    next(iterator.next());
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 제너레이터에 비동기 동작 추가

한번 콘솔로 돌려보자. 일반 제너레이터 함수와의 동작이 비교될 것이다.

모르겠다.

async function* generateSequence(start, end) {
  for (let i = start; i <= end; i++) {
    // await를 사용할 수 있습니다!
    await new Promise(resolve => setTimeout(resolve, 1000));
    yield i;
  }
}

(async () => {
  let generator = generateSequence(1, 5);
  for await (let value of generator) {
    alert(value); // 1, 2, 3, 4, 5
  }
})();
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# Reference & Comment

  • https://gitlab.com/siots-study/topics/-/wikis/asyncronous
  • https://www.youtube.com/watch?v=JaHlR1IGLN8&list=PL7jH19IHhOLMmmjrwCi7-dMFVdoU0hhgF
  • https://helloworldjavascript.net/pages/285-async.html
  • https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
  • https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
  • https://ko.javascript.info/async
  • https://ko.javascript.info/generators-iterators
  • https://www.bsidesoft.com/8325
  • https://www.bsidesoft.com/6037